using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Soap;
using System.Xml.Serialization;

namespace Pegasus.Runtime.Serialization
{
	/// <summary>
	/// Summary description for DataMapper.
	/// </summary>
	[Obsolete( "Use SqlObjectMapper in place of this class.", false )]
	public class DataMapper
	{
		/// <summary>
		/// Hide the constructor.
		/// </summary>
		private DataMapper()
		{
		}
		
		private static Hashtable serializerCache = new Hashtable( 10 );
		private static XmlRootAttribute xRoot = new XmlRootAttribute( "MyVery_CustomRoot" );


		/// <summary>
		/// Map the data in the source object to a new instance of the destination object.
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destination">An instance of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static public object MapIt( object source, object destination )
		{
			//Type sourceType = (source is Type) ? (Type)source : source.GetType();
			Type destinationType = (destination is Type) ? (Type)destination : destination.GetType();

			return MapIt( source, destinationType );
		}
		
		private static XmlSerializer GetUniqueSerializerFromCache( Type type )
		{
			XmlSerializer serializer = null;
			if ( serializerCache.ContainsKey( type ) )
			{
				serializer = serializerCache[ type ] as XmlSerializer;
			}
			else
			{
				serializer = new XmlSerializer( type, xRoot );
				serializerCache[ type ] = serializer;
			}
			
			return serializer;
		}


		/// <summary>
		/// Map the data in the source object to a new instance of the destination object.
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destinationType">The type of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static public object MapIt( object source, Type destinationType )
		{

			// Create the Serializer for each of the objects
			XmlSerializer sourceSerializer = GetUniqueSerializerFromCache( source.GetType() );
			XmlSerializer destinationSerializer = GetUniqueSerializerFromCache( destinationType );
			

			// Get the namespaces for each of the objects
			string sourceNamespace = GetNamespace( source );
			string destinationNamespace = GetNamespace( destinationType );

			// Serialize the object and render into a string
			MemoryStream stream = new MemoryStream();
			sourceSerializer.Serialize( stream, source );
			stream.Seek( 0, SeekOrigin.Begin );
			string data = new StreamReader(stream).ReadToEnd();
			
			

			// Replace the namespace declaration in the xml from one object to the other 
			string changed = data;
			if( sourceNamespace != null )
			{
				changed = data.Replace( string.Format(@" xmlns=""{0}"">", sourceNamespace), string.Format(@" xmlns=""{0}"">", destinationNamespace) );
			}

			// Re-constitute the object from the modified XML String
			StringReader sreader = new StringReader( changed );
			object obj = destinationSerializer.Deserialize( sreader );		// instanciate the object
	
			// close the streams
			stream.Close();
			sreader.Close();
			
			return obj;
		}


		/// <summary>
		/// Get the XmlType attribute's namespace
		/// </summary>
		/// <param name="obj">Instance of the object or type of object</param>
		/// <returns>string value of the namespace specified in the XmlType's Namespace property</returns>
		private static string GetNamespace( object obj )
		{
			Type type = (obj is Type) ? (Type)obj : obj.GetType();
			foreach( object attribute in type.GetCustomAttributes(true) )
			{
				if( attribute is XmlTypeAttribute )
				{
					return ((XmlTypeAttribute)attribute).Namespace;
				}
			}

			return null;
		}

		/// <summary>
		/// Map the data in the source object to a new instance of the destination object.
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destinationType">The type of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static public object MapIt_Soap( object source, Type destinationType )
		{
			MemoryStream stream = null;
			
			try
			{
				// Create the Soap Formatter
				SoapFormatter sourceFormatter = new SoapFormatter();


				DumpAttributes( source );
				DumpAttributes( destinationType );


				// Serialize the object and render into a string
				stream = new MemoryStream();
				sourceFormatter.Serialize( stream, source );

				// Capture the string generated be the soap formatter's serialize
				stream.Seek( 0, SeekOrigin.Begin );
				string data = new StreamReader(stream).ReadToEnd();

				// Re-constitute the object from the modified XML String
				stream.Seek( 0, SeekOrigin.Begin );
				sourceFormatter.Binder = new MyBinder();
				sourceFormatter.Binder.BindToType( destinationType.Assembly.FullName, destinationType.FullName );
				object obj = sourceFormatter.Deserialize( stream );
				return obj;
			}
			catch( Exception ex )
			{
				Debug.WriteLine( ex );
				throw;
			}
			finally
			{
				if ( stream != null )
				{
					stream.Close();
				}
			}
		}


		/// <summary>
		/// Dump the Attribute to the debug output screen
		/// </summary>
		private static void DumpAttributes( object obj )
		{
			Type type = (obj is Type) ? (Type)obj : obj.GetType();
			Debug.WriteLine( string.Format( "Attributes for {0}", obj ) );
			foreach( object attribute in type.GetCustomAttributes(true) )
			{
				Debug.WriteLine( string.Format( "    {0}", attribute ) );
			}
		}


		/// <summary>
		/// An attempt a a
		/// </summary>
		sealed private class MyBinder : System.Runtime.Serialization.SerializationBinder
		{
			public override Type BindToType( string assemblyName, string typeName )
			{
				// The following line of code returns the type.
				Type typeToDeserialize = Type.GetType( string.Format("{0}, {1}", typeName, assemblyName) );

				return typeToDeserialize;
			}

		}


		/// <summary>
		/// Map the data in the source object to a new instance of the destination object using reflection
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destinationType">The type of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static public object MapIt_Reflection( object source, Type destinationType )
		{
			if( source == null )
			{
				return null;
			}

			// find the default constructor
			ConstructorInfo constructorDefault = destinationType.GetConstructor( new Type[0] );
			if( constructorDefault == null )
			{ 
				// find the constructor taking a string argument
				ConstructorInfo constructorTakingString = destinationType.GetConstructor( new Type[]{ typeof(string) } );
				if( constructorTakingString == null )
					return null;
				else
					return constructorTakingString.Invoke( new string[]{ source.ToString() } );
			}

			// Do the actual work now
			try
			{
				object destination = constructorDefault.Invoke( new object[0] );
				
				// Reflect all fileds to the new object
				ReflectFields( source, destination );

				// Reflect all properties to the new object
				ReflectProperties( source, destination );

				return destination;
			}
			catch( Exception ex )
			{
				Debug.WriteLine( ex );
				throw;
			}
		}

		/// <summary>
		/// Maps the it_ enum.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="destination">The destination.</param>
		/// <returns></returns>
		static	public	object	MapIt_Enum(
			object	source,
			object	destination
			)
		{
			//subDesc.Duration = (MFN.WebServices.Data.ECommerceData.Subscription.DurationEnum)Enum.Parse( typeof( MFN.WebServices.Data.ECommerceData.Subscription.DurationEnum ), subscriptionPricing.Duration.ToString(), true );
			
			return( Enum.Parse( destination.GetType(), source.ToString(), true ) );
		}


		/// <summary>
		/// Map the data in the source object to a new instance of the destination object using reflection
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destination">The type of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static protected void ReflectFields( object source, object destination )
		{
			Type sourceType = (source is Type) ? (Type)source : source.GetType();
			Type destinationType = (source is Type) ? (Type)destination : destination.GetType();

			FieldInfo[] fields = sourceType.GetFields( BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.GetField | BindingFlags.Instance );
			foreach( FieldInfo source_info in fields )
			{
				MemberInfo dest_info;
				dest_info = destination.GetType().GetField( source_info.Name );
				if( dest_info != null ) 
				{
					ReflectDude( source, source_info, destination, dest_info );
				}
				else
				{
					dest_info = destination.GetType().GetProperty( source_info.Name );
					if( dest_info != null ) 
					{
						ReflectDude( source, source_info, destination, dest_info );
					}
				}
			}
		}


		/// <summary>
		/// Map the data in the source object to a new instance of the destination object using reflection
		/// </summary>
		/// <param name="source">Instance of the data object to be coppied</param>
		/// <param name="destination">The type of object to be returned</param>
		/// <returns>An new instance of the destination object containing the data valued from the source</returns>
		static protected void ReflectProperties( object source, object destination )
		{
			Type sourceType = (source is Type) ? (Type)source : source.GetType();
			Type destinationType = (source is Type) ? (Type)destination : destination.GetType();

			PropertyInfo[] props = sourceType.GetProperties( BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.GetProperty | BindingFlags.Instance );
			foreach( PropertyInfo source_info in props )
			{
				MemberInfo dest_info;
				dest_info = destination.GetType().GetProperty( source_info.Name );
				if( dest_info != null ) 
				{
					ReflectDude( source, source_info, destination, dest_info );
				}
				dest_info = destination.GetType().GetField( source_info.Name );
				if( dest_info != null ) 
				{
					ReflectDude( source, source_info, destination, dest_info );
				}
			}
		}


		/// <summary>
		/// This dude maps Fields to Properties and visa-versa
		/// </summary>
		static protected void ReflectDude( object source, MemberInfo source_info, object destination, MemberInfo destination_info )
		{
			Type source_x_type    = null;
			object source_x_value = null;

			Type destination_x_type = null;
			object destination_x_value = null;


			// Determine the Source type and value
			if( source_info is FieldInfo ) 
			{
				FieldInfo info = (FieldInfo) source_info; 
				source_x_value = info.GetValue( source );
				source_x_type  = info.FieldType;
			}
			else if( source_info is PropertyInfo )
			{
				PropertyInfo info = (PropertyInfo) source_info; 
				source_x_value    = info.GetValue( source, null );
				source_x_type     = info.PropertyType;
			}

			// Determine the Source type and value
			if( destination_info is FieldInfo ) 
			{
				FieldInfo info      = (FieldInfo) destination_info; 
				destination_x_value = info.GetValue( destination );
				destination_x_type  = info.FieldType;
			}
			else if( destination_info is PropertyInfo )
			{
				PropertyInfo info   = (PropertyInfo) destination_info; 
				destination_x_value = info.GetValue( destination, null );
				destination_x_type  = info.PropertyType;
			}

			// Now figure out what to do to whom
			if( destination_info.DeclaringType.IsClass )
			{
				if( source_x_type == destination_x_type )
				{
					DoSetValue( destination, destination_info, source_x_value );
				}
				else if( destination_x_type.IsEnum && source_x_type.IsEnum )
				{
					object o = Enum.Parse( destination_x_type, source_x_value.ToString());
					DoSetValue( destination, destination_info, o );
				}
				else if( destination_x_type.IsArray && source_x_type.IsArray )
				{
					object o = MapArray_Reflection( source_x_value, destination_x_type );
					DoSetValue( destination, destination_info, o );
				}
				else 
				{
					// WARNING!!!  Going recursive!
					object o = MapIt_Reflection( source_x_value, destination_x_type );
					DoSetValue( destination, destination_info, o );
				}
			}
			else
			{
				DoSetValue( destination, destination_info, source_x_value );
			}
		}


		/// <summary>
		/// Set the destination object and property/field to the new value
		/// </summary>
		static protected void DoSetValue( object destination, MemberInfo destination_info, object new_value )
		{
			if( destination_info is FieldInfo )
			{
				((FieldInfo)destination_info).SetValue( destination, new_value ); 
			}
			else
			{
				((PropertyInfo)destination_info).SetValue( destination, new_value, null );
			}
		}

		/// <summary>
		/// Create an array of objects using reflection
		/// </summary>
		/// <param name="source">Source array of objects</param>
		/// <param name="type">Type of the new array of object</param>
		/// <returns></returns>
		protected static object[] MapArray_Reflection( object source, Type type )
		{
			// Like kinds of arrays 
			if( type == source.GetType().GetElementType() )
			{
				return (object[])source;
			}

			ArrayList tempList = new ArrayList( );

			foreach( object element in (object[])source )
			{
				tempList.Add( MapIt_Reflection( element, type.GetElementType() ) );
			}

			return (object[]) tempList.ToArray( type.GetElementType() );
		}
	}
}
